﻿#define BYTESONLY

using System;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using O1.IO.Storage;

namespace Tests.O1.IO.Storage
{
    [TestClass]
    public class StorgageBlockTests
    {
#if BYTESONLY
        private static byte[][] randomData = BuildRandomData();
#endif
#if !BYTESONLY
        private static Spinlock[] randomData = BuildRandomData();
#endif
        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockCanAllocate0Size()
        {
            Action test = () => new StorageBlock(0);
            test.ShouldNotThrow();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockCanAllocate0SizeAmatorizeJIT()
        {
            Action test = () => new StorageBlock(0);
            test.ShouldNotThrow();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockCanAllocateBlockSizeExactly()
        {
            Action test = () => new StorageBlock(StorageBlock.BlockSize);
            test.ShouldNotThrow();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockLengthMatches0Size()
        {
            var block = new StorageBlock(0);
            block.Length.Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockLengthMatchesExactBlockSize()
        {
            var block = new StorageBlock(StorageBlock.BlockSize * 2);
            block.Length.Should().Be(StorageBlock.BlockSize * 2);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockSetsAndGetsDataAt3BlocksWithReferenceAssertions()
        {
            var block = new StorageBlock(randomData.Length);
            for (var i = 0; i < randomData.Length; i++)
            {
                block[i] = randomData[i];
            }

            for (var i = 0; i < randomData.Length; i++)
            {
                block[i].Should().BeSameAs(randomData[i]);
            }
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockSetsAndGetsDataAt3BlocksWithoutAssertions()
        {
            var block = new StorageBlock(randomData.Length);
            for (var i = 0; i < randomData.Length; i++)
            {
                block[i] = randomData[i];
            }

            for (var i = 0; i < randomData.Length; i++)
            {
#if BYTESONLY
                block[i] = randomData[i];
#endif
#if !BYTESONLY
                block[i].SpinWrite(randomData[i].SpinRead());
#endif
            }
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockSetsAndGetsDataAtSingleExecutionWithoutAssertions()
        {
            var block = new StorageBlock(randomData.Length);
            block[StorageBlock.BlockSize - 1] = randomData[StorageBlock.BlockSize - 1];
#if BYTESONLY
            block[StorageBlock.BlockSize - 1].Should().NotBeNull();
#endif
#if !BYTESONLY
            block[StorageBlock.BlockSize - 1].SpinRead().Should().NotBeNull();
#endif
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockDirtyReadDataAt3BlocksWithoutAssertions()
        {
            var block = new StorageBlock(randomData.Length);
            for (var i = 0; i < randomData.Length; i++)
            {
                block[i] = randomData[i];
            }

            for (var i = 0; i < randomData.Length; i++)
            {
#if BYTESONLY
                block[i].Should().BeSameAs(randomData[i]);
#endif
#if !BYTESONLY
                block[i].DirtyRead().Should().BeSameAs(randomData[i].DirtyRead());
#endif
            }
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockExpandDoesNothingWhenNewSizeIs0()
        {
            var block = new StorageBlock(1000);
            block.Length.Should().Be(1000);
            block.Expand(0);
            block.Length.Should().Be(1000);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockExpandDoesNothingWhenNewSizeIsNegative()
        {
            var block = new StorageBlock(1000);
            block.Length.Should().Be(1000);
            block.Expand(-100);
            block.Length.Should().Be(1000);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockExpandDoesNothingWhenNewSizeDoesNotIncrease()
        {
            var block = new StorageBlock(1000);
            block.Length.Should().Be(1000);
            block.Expand(10);
            block.Length.Should().Be(1000);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockExpandDoesNothingWhenNewSizeIsEqual()
        {
            var block = new StorageBlock(1000);
            block.Length.Should().Be(1000);
            block.Expand(1000);
            block.Length.Should().Be(1000);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockExpandIncreasesSizeWhenNewSizeIsGreaterButStillOneBlock()
        {
            var block = new StorageBlock(1000);
            block.Length.Should().Be(1000);
            block.Expand(2000);
            block.Length.Should().Be(2000);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StorageBlockExpandIncreasesSizeWhenNewSizeIsGreater()
        {
            var block = new StorageBlock(1000 + StorageBlock.BlockSize);
            block.Length.Should().Be(1000 + StorageBlock.BlockSize);
            block.Expand(2000 + StorageBlock.BlockSize);
            block.Length.Should().Be(2000 + StorageBlock.BlockSize);
        }
#if BYTESONLY
        private static byte[][] BuildRandomData()
        {
            var rnd = new Random();
            var data = new byte[(StorageBlock.BlockSize) + 99][];
            for (var i = 0; i < data.Length; i++)
            {
                var buffer = new byte[4];
                rnd.NextBytes(buffer);
                var slock = new ConcurrentWriter();
                slock.SpinWrite(buffer);
                data[i] = buffer;
            }
            
            return data;
        }
#endif
#if !BYTESONLY
        private static Spinlock[] BuildRandomData()
        {
            var rnd = new Random();
            var data = new Spinlock[(StorageBlock.BlockSize) + 99];
            for (var i = 0; i < data.Length; i++)
            {
                var buffer = new byte[4];
                rnd.NextBytes(buffer);
                var slock = new Spinlock();
                slock.SpinWrite(buffer);
                data[i] = slock;
            }
            
            return data;
        }
#endif
    }
}
